package com.gaofans.vertx.boot.web.route;

import com.gaofans.vertx.boot.web.annotation.Authorities;
import com.gaofans.vertx.boot.web.annotation.ResponseBody;
import com.gaofans.vertx.boot.web.annotation.RouteMapping;
import com.gaofans.vertx.boot.web.annotation.TemplateRender;
import com.gaofans.vertx.boot.web.auth.AuthorityDefinition;
import com.gaofans.vertx.boot.web.utils.UriUtil;
import com.google.common.collect.Lists;
import io.vertx.core.Handler;
import io.vertx.core.http.HttpMethod;
import io.vertx.ext.web.RoutingContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Controller;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;

/**
 * 查找路由
 * @author GaoFans
 */
public class DefaultRouteDiscoverer implements ApplicationContextAware,RouteDiscoverer {

    private ApplicationContext applicationContext;
    private static final Logger LOG = LoggerFactory.getLogger(DefaultRouteDiscoverer.class);
    private Collection<RouteDefinition> routeDefinitions;

    @Override
    @SuppressWarnings("all")
    public synchronized Collection<RouteDefinition> findRoutes() {
        if(routeDefinitions != null){
            return routeDefinitions;
        }
        //由注解创建
        String[] beanNames = this.applicationContext.getBeanNamesForAnnotation(Controller.class);
        ArrayList<RouteDefinition> routes = Lists.newArrayListWithCapacity(beanNames.length);
        for (String beanName : beanNames) {
            Object bean = this.applicationContext.getBean(beanName);
            RouteMapping mapping = this.applicationContext.findAnnotationOnBean(beanName, RouteMapping.class);
            String topPath = "";
            if(mapping != null){
                topPath = mapping.path();
            }
            boolean isResponseBody = false;
            ResponseBody responseBody = this.applicationContext.findAnnotationOnBean(beanName, ResponseBody.class);
            if(responseBody != null){
                isResponseBody = true;
            }
            Class<?> beanClass = bean.getClass();
            for (Method method : beanClass.getMethods()) {
                RouteMapping methodMapping = AnnotationUtils.findAnnotation(method, RouteMapping.class);
                if(methodMapping != null){
                    String routePath = methodMapping.path();
                    HttpMethod[] routeMethod = methodMapping.method().length==0?mapping.method():methodMapping.method();
                    RouteDefinition routeDefinition = new RouteDefinition();
                    routeDefinition.setPath(UriUtil.concatRouteUri(topPath,routePath));
                    routeDefinition.setHttpMethod(routeMethod);
                    routeDefinition.setOrder(methodMapping.order());
                    routeDefinition.setHandler((Handler<RoutingContext>) ReflectionUtils.invokeMethod(method,bean));
                    routeDefinition.setConsumes(methodMapping.consumes());
                    routeDefinition.setHeaders(methodMapping.headers());
                    routeDefinition.setProduces(methodMapping.produces());
                    routeDefinition.setExcludeHandlers(methodMapping.excludeHandlers());
                    routeDefinition.setIncludeHandlers(methodMapping.handlers());
                    routeDefinition.setRequestBody(methodMapping.requestBody());
                    Authorities authorities = AnnotationUtils.findAnnotation(method, Authorities.class);
                    if(authorities != null){
                        routeDefinition.setAuthorityDefinition(new AuthorityDefinition(authorities.value(),authorities.position()));
                    }
                    TemplateRender templateRender = AnnotationUtils.findAnnotation(method, TemplateRender.class);
                    if(templateRender != null){
                        routeDefinition.setTemplateRender(true);
                    }
                    if(isResponseBody){
                        routeDefinition.setResponseBody(true);
                    }else{
                        if(AnnotationUtils.findAnnotation(method, ResponseBody.class) != null){
                            routeDefinition.setResponseBody(true);
                        }
                    }
                    routes.add(routeDefinition);
                    LOG.info("找到route: uri[{}],method[{}]",routeDefinition.getPath(),routeDefinition.getHttpMethod());
                }
            }
        }
        //接口查找
        beanNames = this.applicationContext.getBeanNamesForType(RouteConfigurable.class);
        for (String beanName : beanNames) {
            if(this.applicationContext.findAnnotationOnBean(beanName,RouteMapping.class) != null){
                continue;
            }
            RouteConfigurable configurable = this.applicationContext.getBean(beanName, RouteConfigurable.class);
            RouteDefinition routeDefinition = new RouteDefinition();
            routeDefinition.setPath(configurable.getPath());
            routeDefinition.setHttpMethod(configurable.getHttpMethod());
            routeDefinition.setOrder(configurable.getOrder());
            routeDefinition.setRequestBody(configurable.requestBody());
            routeDefinition.setHandler(configurable.getHandler());
            routeDefinition.setConsumes(configurable.getConsumes());
            routeDefinition.setHeaders(configurable.getHeaders());
            routeDefinition.setProduces(configurable.getProduces());
            routeDefinition.setExcludeHandlers(configurable.getExcludeHandlers());
            routeDefinition.setIncludeHandlers(configurable.getIncludeHandlers());
            routeDefinition.setAuthorityDefinition(configurable.getAuthorityDefinition());
            routeDefinition.setTemplateRender(configurable.isTemplateRender());
            routeDefinition.setRequestBody(configurable.requestBody());
            routes.add(routeDefinition);
            LOG.info("找到route: uri[{}],method[{}]",routeDefinition.getPath(),routeDefinition.getHttpMethod());
        }
        if(routes.size() == 0){
            LOG.warn("未找到route");
        }
        routeDefinitions  = Collections.unmodifiableCollection(routes);
        return routes;
    }

    @Override
    public void setApplicationContext(@NonNull ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

}
